#include <sstream>
#include <iostream>
#include <fstream>
#include <thread>
#include "core.hpp"

#if defined(TARGET_LINUX)
    #include <unistd.h>
    #include <sys/stat.h>
    
    #define _access     access
    #define _mkdir(v)   mkdir((v), 0755)
#else
    #include <direct.h>
    #include <io.h>
#endif

namespace freecraft
{

std::list<Chunk*> Chunk::chunkList;
const std::string Chunk::CHUNK_SAVE_PATH("chunks/");

Chunk::Chunk()
{
    for (int i=0; i<CHUNK_SIZE; i++)
        for (int k=0; k<CHUNK_SIZE; k++)
        {
            ElementId elementId = ElementId::BASE;
            ObjectId objectId = ObjectId::BASE;

            if (k == 1)
            {
                elementId = ElementId::STAIR;
                objectId = ObjectId::STAIR;
            }
            for (int j=0; j<CHUNK_SIZE; j++)
            {
                this->elements[i][k][j].elementId = elementId; 
                this->elements[i][k][j].objectId = objectId;           
            }
        }

    Chunk::chunkList.push_back(this);
}

Chunk::Chunk(const glm::ivec3 &pos)
{
    this->position = pos;
    loadChunk(pos, *this);
    Chunk::chunkList.push_back(this);
}

bool Chunk::isSurrounded(const glm::ivec3 &pos)
{
    Element surround;

    if (!isSolidElement(pos.x + 1, pos.y, pos.z)) return false;
    if (!isSolidElement(pos.x - 1, pos.y, pos.z)) return false;
    if (!isSolidElement(pos.x, pos.y + 1, pos.z)) return false;
    if (!isSolidElement(pos.x, pos.y - 1, pos.z)) return false;
    if (!isSolidElement(pos.x, pos.y, pos.z + 1)) return false;
    if (!isSolidElement(pos.x, pos.y, pos.z - 1)) return false;

    return true;
}

bool Chunk::isSurrounded(int x, int y, int z)
{
    glm::ivec3 pos(x, y, z);
    return Chunk::isSurrounded(pos);
}

bool Chunk::getElement(const glm::ivec3 &pos, Element &element)
{
    if (pos.x < 0) return false;
    if (pos.y < 0) return false;
    if (pos.z < 0) return false;

    if (pos.x >= Chunk::CHUNK_SIZE) return false;
    if (pos.y >= Chunk::CHUNK_SIZE) return false;
    if (pos.z >= Chunk::CHUNK_SIZE) return false;

    Chunk* chunk = chunkList.front();
    if (chunk == nullptr) return false;

    element = chunk->elements[pos.x][pos.y][pos.z];

    return true;
}

bool Chunk::getElement(int x, int y, int z, Element &element)
{
    glm::ivec3 pos(x, y, z);
    return Chunk::getElement(pos, element);
}

Element *Chunk::getElement(const glm::ivec3 &pos)
{
    if (pos.x < 0) return nullptr;
    if (pos.y < 0) return nullptr;
    if (pos.z < 0) return nullptr;

    if (pos.x >= Chunk::CHUNK_SIZE) return nullptr;
    if (pos.y >= Chunk::CHUNK_SIZE) return nullptr;
    if (pos.z >= Chunk::CHUNK_SIZE) return nullptr;

    Chunk* chunk = chunkList.front();
    if (chunk == nullptr) return nullptr;

    return &chunk->elements[pos.x][pos.y][pos.z];
}

bool Chunk::isSolidElement(freecraft::Element &element)
{
    if ((element.elementId == ElementId::AIR) || (element.objectId != ObjectId::BASE))
        return false;
    return true;
}

bool Chunk::isSolidElement(int x, int y, int z)
{
    Element element;
    if (!getElement(x, y, z, element)) return false;

    return Chunk::isSolidElement(element);
}

Chunk *Chunk::getChunk(const glm::ivec3 &pos)
{
    for (std::list<Chunk*>::iterator iter=chunkList.begin(); iter!=chunkList.end(); ++iter)
    {
        if ((*iter)->position == pos)
            return *iter;
    }

    return nullptr;
}

bool Chunk::loadChunk(const glm::ivec3 &pos, Chunk &chunk)
{
    chunk.position = pos;

    std::string path;
    formatChunkPath(pos, path);

    std::ifstream file(path);

    if (file.fail())
    {
        file.close();
        
        if (!generateChunk(chunk))
        {
            std::cout << "generate chunk failed: " << path << std::endl;
            return false;
        }

        return true;
    }

    bool findChunk = false;
    while (1)
    {
        int chunky;
        file >> chunky;

        if (file.eof())
            break;

        if (chunky == pos.y)
        {
            findChunk = true;
            glm::ivec3 p;
            file >> p.x >> p.y >> p.z;
            if (!checkElementPos(p))
            {
                std::cout << "read wrong element position: " << p.x << ' ' << p.y << ' ' << p.z << std::endl;
                return false;
            }

            Element &e = chunk.elements[p.x][p.y][p.z];
            file >> (int&)e.elementId >> (int&)e.objectId;
        }
        else
        {
            std::string stupidBuffer;
            std::getline(file, stupidBuffer);
        }
    }

    if (!findChunk)
    {
        if (!generateChunk(chunk))
        {
            std::cout << "generate chunk failed: " << path << std::endl;
            return false;
        } 
    }

    return true;
}

bool Chunk::generateChunk(freecraft::Chunk &chunk)
{
    for (int i=0; i<CHUNK_SIZE; i++)
        for (int j=0; j<CHUNK_SIZE / 2; j++)
            for (int k=0; k<CHUNK_SIZE; k++)
            {
                Element &e = chunk.elements[i][j][k];

                e.elementId = ElementId::BASE;
                e.objectId = ObjectId::BASE;
            }

    std::thread t(saveChunk, std::ref(chunk));
    t.detach();

    return true;
}

void Chunk::saveChunk(freecraft::Chunk &chunk)
{
    if (_access(CHUNK_SAVE_PATH.c_str(), 0) != 0)
    {
        if (_mkdir(CHUNK_SAVE_PATH.c_str()) != 0)
        {
            std::cout << "failed to creat chunks folder!\n";
            return;
        }
    }

    std::string path;
    formatChunkPath(chunk.position, path);

    std::ofstream file(path, std::ios::out | std::ios::app);

    if (file.fail())
    {
        file.close();
        std::cout << "open file failed: " << path << std::endl;
        return;
    }
    // y ex ey ez eid oid
    for (int i=0; i<CHUNK_SIZE; i++)
        for (int j=0; j<CHUNK_SIZE; j++)
        {
            for (int k=0; k<CHUNK_SIZE; k++)
            {
                Element &element = chunk.elements[i][j][k];
                if (element.elementId == ElementId::AIR)
                    continue;

                file << chunk.position.y << ' ' << i 
                    << ' ' << j 
                    << ' ' << k
                    << ' ' << (int)element.elementId
                    << ' ' << (int)element.objectId
                    << std::endl;
            }
        }

    file.close();
    
    std::cout <<"save chunk success: " << path << std::endl;
}

void Chunk::formatChunkPath(const glm::ivec3 &pos, std::string &path)
{
    std::stringstream buffer;
    buffer << CHUNK_SAVE_PATH << pos.x << '-' << pos.z << ".chunk";
    path = buffer.str();
}

bool Chunk::checkElementPos(const glm::ivec3 &pos)
{
    if (pos.x >= CHUNK_SIZE) return false;
    if (pos.y >= CHUNK_SIZE) return false;
    if (pos.z >= CHUNK_SIZE) return false;

    if (pos.x < 0) return false;
    if (pos.y < 0) return false;
    if (pos.z < 0) return false;

    return true;
}

}
